package com.kaka.jsonmap.core;

import com.kaka.jsonmap.core.evaluator.CurrJsonPathEvaluator;
import com.kaka.jsonmap.core.evaluator.ExpressionEvaluator;
import com.kaka.jsonmap.core.evaluator.GlobalJsonPathEvaluator;
import com.kaka.jsonmap.core.expection.RequiredFieldValidationException;
import com.kaka.jsonmap.core.mapper.DefaultFieldMapper;
import com.jayway.jsonpath.internal.Utils;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
public class ValueTransformer {
    private final List<ExpressionEvaluator> evaluators;

    /**
     * Constructs a ValueTransformer with the given strategies, evaluators, and dictionary manager.
     *
     * @param evaluators List of expression evaluators
     */
    public ValueTransformer(List<ExpressionEvaluator> evaluators) {
        this.evaluators = evaluators != null ? new ArrayList<>(evaluators) : Collections.emptyList();
    }

    private boolean isCurrJsonPathEvaluator(String expression) {
        if (this.evaluators.stream()
                .filter(x -> x instanceof CurrJsonPathEvaluator)
                .anyMatch(e -> e.supports(expression))) {
            return true;
        } else if (this.evaluators.stream()
                .filter(x -> x instanceof GlobalJsonPathEvaluator)
                .anyMatch(e -> e.supports(expression))) {
            return false;
        }
        throw new IllegalArgumentException(String.format("expression is not a valid curr json path expression: %s", expression));
    }


    public Boolean isCurrJsonPathEvaluator(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            return isCurrJsonPathEvaluator((String) value);
        }
        if (value instanceof Map) {
            Map<String, Object> valueMap = (Map<String, Object>) value;
            if (valueMap.containsKey("value")) {
                return isCurrJsonPathEvaluator((String) valueMap.get("value"));
            }
        }
        return null;
    }

    /**
     * Transforms the given value based on mapping rules and strategies.
     *
     * @param key        The JSONPath key for the value
     * @param value      The input value to transform
     * @param rootObject The root object for context
     * @return The transformed value
     */
    public Object transformValue(String key,
                                 Object value,
                                 Map<String, Object> rootObject) {
        if (value == null) {
            return value;
        }
        // value is a map representing a field with validation rules
        if (value instanceof Map) {
            Map<String, Object> valueMap = (Map<String, Object>) value;
            if (valueMap.containsKey("value")) {
                String rule = (String) valueMap.get("value");
                Boolean required = (Boolean) valueMap.getOrDefault("required", true);
                String message = (String) valueMap.getOrDefault("message",null);
                String format = (String) valueMap.get("format");
                Object result = this.transformValue(key, rule, rootObject);
                if (required && result == null) {
                    log.error("Validation failed: {} (JSONPath: {})", message, rule);
                    throw new RequiredFieldValidationException(String.format("key:%s,value:%s field is required.", key, rule));
                }
                if (result != null && format != null) {
                    result = this.formatDate(result, format);
                }
                return result;
            }
            return new DefaultFieldMapper(this).mapFields(valueMap, rootObject);
        }

        if (!(value instanceof String)) {
            return handleNonStringValue(key, value, rootObject);
        }

        String expression = (String) value;
        for (ExpressionEvaluator evaluator : evaluators) {
            if (evaluator.supports(expression)) {
                Object result = evaluator.evaluate(expression, rootObject);
                Utils.isTrue(result != null || !evaluator.getClass().getSimpleName().contains("RandomIfEmpty"),
                        "Mapping strategy " + evaluator.getClass().getSimpleName() + " failed to process: " + expression);
                return result;
            }
        }

        return expression;
    }

    /**
     * Handles non-string values, such as lists, by recursively transforming each item.
     *
     * @param value      The non-string value to transform
     * @param rootObject The root object
     * @return The transformed value
     */
    private Object handleNonStringValue(String key, Object value, Map<String, Object> rootObject) {
        if (value instanceof List) {
            List<Object> nestedList = (List<Object>) value;
            List<Object> mappedList = new ArrayList<>();
            for (Object item : nestedList) {
                mappedList.add(transformValue(key, item, rootObject));
            }
            return mappedList;
        }
        return value;
    }

    /**
     * Formats a date value according to the specified format.
     *
     * @param value  The value to format (Date or String)
     * @param format The date format string
     * @return The formatted date string or original value if formatting fails
     */
    private Object formatDate(Object value, String format) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat(format);
            if (value instanceof Date) {
                return sdf.format((Date) value);
            } else if (value instanceof String) {
                String dateStr = (String) value;
                SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
                parser.setLenient(false);
                Date date = parser.parse(dateStr);
                return sdf.format(date);
            } else {
                log.warn("Unsupported date value type for formatting: {}", value.getClass().getName());
                return value.toString();
            }
        } catch (Exception e) {
            log.error("Error formatting date: {} with format: {}, error: {}", value, format, e.getMessage());
            return value;
        }
    }

}

